from deslab.src.automatadefs import fsa
from deslab.src.algorithms import *
from deslab.src.utilities import syms
from deslab.src.def_const import *
from deslab.src.exceptions import *
from deslab.src.graphs import *

"""This is the toolbox for failure diagnosis. It contains algorithms
for building a diagnoser a verifier and to test diagnosability
 """

def labelfailaut(sf):
    """
    This function creates a label failure automaton in 
    the context of failure diagnosis
    """
    # first we check if input parameters were correctly passed
    if isinstance(sf, list):
        sf = sf[0]
    elif isinstance(sf,set):
        sf = list(sf)[0]
    elif isinstance(sf,frozenset):
        sf = list(sf)[0]     
    elif isinstance(sf,str):
        pass   
    else:
        raise invalidArgument, 'sf must be string, list or set'
     
    Y,N = syms('Y N')
    sigmaA = [sf]
    XA = [Y,N]
    X0A = [N]
    XmA = []
    T = [(N,sf,Y),(Y,sf,Y)]
    Al=fsa(XA,sigmaA,T,X0A,XmA, Sigobs=EMPTYSET, name='Al')
    return Al

def is_diagnosable_verif(G, sf, Sigma_o=UNDEFINED):
    """ This function implements the verifier proposed by Basilio
    and Moreira """
    
    # first we check if input parameters were correctly passed
    if isinstance(sf, list):
        sf = sf[0]
    elif isinstance(sf,set):
        sf = list(sf)[0]
    elif isinstance(sf,frozenset):
        sf = list(sf)[0]  
    elif isinstance(sf, str):
        pass      
    else:
        raise invalidArgument, 'sf must be string, list or set'
    # now we check the input parameter
    if (Sigma_o == UNDEFINED) : # Sigma_o was not defined
        Sigma_obs = G.Sigobs     
    elif isinstance(Sigma_o,list) | isinstance(Sigma_o,set):      
        Sigma_obs = frozenset(Sigma_o)                
    elif isinstance(Sigma_o, frozenset):
        Sigma_obs = Sigma_o     
    else:
       raise invalidArgument, 'Observed set must be of type set or list'   
    # STEP 1   
    Sigma_uo = G.Sigma - Sigma_obs    
    GN_prime = G.deletevent(sf)    
    GN = ac(GN_prime)    
    # STEP 2
    Al = labelfailaut(sf)
    Gl = G//Al
    Xml = [(x,l) for (x,l) in Gl.X if l=='Y'] 
    Gl = Gl.setpar(Xm=Xml)
    GY = coac(Gl)
    R = [(event,event+'_R') for event in Sigma_uo]
    GN = GN.renamevents(R)
    GV = GN//GY
    XmV = [(xN,(xY,l)) for (xN,(xY,l)) in GV.X if l=='Y']
    GV = GV.setpar(Xm=XmV)     
    SCC_selfloop = [[l] for l in selfloopnodes(GV)]
    SCC_multiple = [C for C in strconncomps(GV) if (len(C)>1)]
    SCC_nontrivial = SCC_selfloop + SCC_multiple
    diagnosable = True
    # finding a counter example
    for C in SCC_nontrivial:
        for x_V in (set(C) & GV.Xm):
            if (GV.Gamma(x_V) & G.Sigma) <> set([]):
                diagnosable = False
                break
    return diagnosable, GV



def diagnoser(G, sf, Sigma_o=UNDEFINED):
    """Function that builds and renders an event Diagnoser
    based on theoretical concepts of Sampath and Lafortune"""
    # first we check if input parameters were correctly passed
    if isinstance(sf, list):
        sf = sf[0]
    elif isinstance(sf,set):
        sf = list(sf)[0]
    elif isinstance(sf,frozenset):
        sf = list(sf)[0] 
    elif isinstance(sf, str):
        pass       
    else:
        raise invalidArgument, 'sf must be string, list or set'
    # we can use the inner observable events for diagnoser computation
    # or the set of observable events passed by the user 
    if (Sigma_o == UNDEFINED) : # Sigma_o was not defined
        Sigma_obs = G.Sigobs     
    elif isinstance(Sigma_o,list) | isinstance(Sigma_o,set):      
        Sigma_obs = frozenset(Sigma_o)                
    elif isinstance(Sigma_o, frozenset):
        Sigma_obs = Sigma_o     
    else:
       raise invalidArgument, 'Observed set must be of type set or list'   
    # we render the automaton using accepted notation
    def latex(D):   
        tex_tab = []     
        for xd in D:
            xd_tex = []
            for x,l in xd:
                xd_tex.append(str(x)+l)            
            xd_tex = reduce(lambda i,j: i+ ','+j, (t for t in xd_tex))
            xd_tex = r'\{' + xd_tex + r'\}'
            tex_tab.append((xd, xd_tex))            
        return dict(tex_tab)
    #label failure automaton   
    A = labelfailaut(sf)  
    D = observer(G//A, Sigma_obs)      
    D = D.setpar(name = 'Diagnoser', table = latex(D))
    D.setgraphic('diagnoser')                   
    return D



def is_diagnosable_diag(G, sf, Sigma_o= UNDEFINED ):   
    """This function looks for indeterminate states, i. e.
    states that belong to indeterminate  SCCs    
     """ 
    if isinstance(sf, list):
        sf = sf[0]
    elif isinstance(sf,set):
        sf = list(sf)[0]
    elif isinstance(sf,frozenset):
        sf = list(sf)[0] 
    elif isinstance(sf, str):
        pass       
    else:
        raise invalidArgument, 'sf must be string, list or set'
    # we can use the inner observable events for diagnoser computation
    # or the set of observable events passed by the user 
    if (Sigma_o == UNDEFINED) : # Sigma_o was not defined
        Sigma_obs = G.Sigobs     
    elif isinstance(Sigma_o,list) | isinstance(Sigma_o,set):      
        Sigma_obs = frozenset(Sigma_o)                
    elif isinstance(Sigma_o, frozenset):
        Sigma_obs = Sigma_o     
    else:
       raise invalidArgument, 'Observed set must be of type set or list'              
    # Calculating the labeling plant          
    A=labelfailaut(sf) 
    Gl = G//A  # G_l
    # Calculating diagnoser
    Gd = diagnoser(G, sf, Sigma_obs)
    Gt= Gd//Gl   
    SCC = strconncomps(Gt)      
    SCC_selfloop = [[l] for l in selfloopnodes(Gt)]
    SCC_multiple = [C for C in SCC if (len(C)>1)]
    SCC_nontrivial = SCC_selfloop + SCC_multiple
    diagnosable = True
    for C in SCC_nontrivial:
        if any(l=='Y' and YN_type(xd)=='YN'  for xd, (x,l) in C):
            diagnosable = False
            break                       
    return diagnosable, Gt


def is_diagnosable_diag1(G, sf, Sigma_o= UNDEFINED ):   
    """This function looks for indeterminate states, i. e.
    states that belong to indeterminate  SCCs    
     """ 
    if isinstance(sf, list):
        sf = sf[0]
    elif isinstance(sf,set):
        sf = list(sf)[0]
    elif isinstance(sf,frozenset):
        sf = list(sf)[0] 
    elif isinstance(sf, str):
        pass       
    else:
        raise invalidArgument, 'sf must be string, list or set'
    # we can use the inner observable events for diagnoser computation
    # or the set of observable events passed by the user 
    if (Sigma_o == UNDEFINED) : # Sigma_o was not defined
        Sigma_obs = G.Sigobs     
    elif isinstance(Sigma_o,list) | isinstance(Sigma_o,set):      
        Sigma_obs = frozenset(Sigma_o)                
    elif isinstance(Sigma_o, frozenset):
        Sigma_obs = Sigma_o     
    else:
       raise invalidArgument, 'Observed set must be of type set or list'              
    # Calculating the labeling plant          
    A=labelfailaut(sf) 
    Gl = G//A  # G_l
    # Calculating diagnoser
    Gd = diagnoser(G, sf, Sigma_obs)
    Gdr = rename_diagnoser(Gd)
    Gt= Gdr//Gl   
    SCC = strconncomps(Gt)      
    SCC_selfloop = [[l] for l in selfloopnodes(Gt)]
    SCC_multiple = [C for C in SCC if (len(C)>1)]
    SCC_nontrivial = SCC_selfloop + SCC_multiple
    diagnosable = True
    for C in SCC_nontrivial:
        if any(l=='Y' and ld=='YN'  for (xd,ld), (x,l) in C):
            diagnosable = False
            break                       
    return diagnosable, Gt


def YN_type(xd):
    """Determines if a diagnoser state is a faulty, normal
    or uncertain state"""
    if all(l=='Y' for x,l in xd):
        return 'Y'
    elif all(l=='N' for x,l in xd):
        return 'N'        
    else:
        return 'YN'


def rename_diagnoser(Gd):
    map=[]    
    for i, xd in enumerate(Gd.X):   
         
        #new_xd = reduce(lambda i,j:  i+j , (x for x  in new_x))        
        if all(l=='Y' for x,l in xd):
            map.append((xd, (i,'Y')))
        elif all(l=='N' for x,l in xd):
            map.append((xd, (i,'N')))
        else:
            map.append((xd,(i,'YN')))          
    Gdren = Gd.renamestates(map)
    Gdren.name = Gd.name     
    return Gdren




#
#
#def observerdelta(self,Eo=EMPTYSET):        
#    X0, X, E, gamma  = self.X0, self.X, self.E,self.gamma,     
#    Xm, type, name, = self.Xm, self.type,self.name, 
#       
#    if Eo <> EMPTYSET :
#        if isinstance(Eo,list):
#            Eunobs = self.E - set(Eo)
#            Eobs = self.E - Eunobs 
#        elif isinstance(Eo,set):
#            Eunobs = self.E - Eo
#            Eobs = Eo
#        else: raise invalidArgument, 'Observable event set must be of type set or list'
#    
#    else:
#        Eunobs = self.E-self.Eobs
#        Eobs = self.Eobs
#                   
#    if (self.is_dfa() &  (Eobs == self.E)):
#        return self 
#    elif not self.is_dfa():
#        raise invalidArgument, 'Automaton passed to this function must be a DFA'
#    
#    
#
#    
#       
#    def extended_gamma(setState):                              
#        activeEvents = EMPTYSET
#        for state in setState:
#            activeEvents=activeEvents | self.gamma(state)
#        activeEvents=activeEvents#-set([epsilon])
#        return activeEvents & Eobs
#    
#    
#    def convertname(stateObserved):
#      
#        stateObserved=list(stateObserved)
#        stateObserved.sort()
#        nameState=''            
#        for atomicState in stateObserved:
#            nameState=nameState+str(atomicState)+','        
#        nameState='\{'+nameState.rstrip(',')+'\}'          
#        return nameState    
#    
#    def unobsreach(setStates): 
#        
#        Ureach = setStates
#        unobsCycle = False  
#                
#        for state in setStates :             
#            Stack = [state]
#            Xreachable = set([state])                             
#            while Stack <> []:
#                currState = Stack.pop()       
#                for sigma in self.gamma(currState) & Eunobs:    
#                    nextState =  self.delta_one_event(currState,sigma)  
#                    nextS = list(nextState)[0]                     
#                    if nextS not in Xreachable:
#                        Xreachable = Xreachable | nextState
#                        Ureach = Ureach | nextState
#                        Stack.append(nextS)
#                    else:  
#                        unobsCycle = True                                            
#        return Ureach, unobsCycle        
#    
#    
#    def delta_ext_unobs(state,event):                              
#        unobsReachSet,unobsCycle1=unobsreach(state) 
#        nextReachedSet=EMPTYSET                           
#        for reachState in unobsReachSet:
#            nextReachedSet=nextReachedSet | self.delta_one_event(reachState,event)    
#        unobsReachSet,unobsCycle2 =unobsreach(nextReachedSet)  
#        unobsCycle = unobsCycle1 | unobsCycle2          
#        return unobsReachSet, unobsCycle   
#    
# 
#    X0obs,unobsCycle = unobsreach(X0)
#    Xobs = [X0obs]
#    Xwaiting = [X0obs]
#    transition = []
#    X0obsname=convertname(X0obs) 
#    delta ='\Delta' 
#    existunobsCycle = False    
#    
#    if unobsCycle:
#        transition.append([convertname(X0obs),delta,convertname(X0obs)])
#        existunobsCycle = True
#    
#    while Xwaiting <> []:
#        stateSet=Xwaiting.pop()                     
#        validSet = extended_gamma(stateSet) 
#        for sigma in validSet:
#            nextState,unobsCycle = delta_ext_unobs(stateSet,sigma)
#            if nextState not in Xobs: 
#                Xobs.append(nextState)
#                Xwaiting.append(nextState) 
#            transition.append([convertname(stateSet),sigma,convertname(nextState)])
#            if unobsCycle:
#                transition.append([convertname(nextState),delta,convertname(nextState)])
#                existunobsCycle = True                                
#    Xmobs,Xobsname = [], []   
#    StateLabel = {}        
#    for observedState in Xobs:
#        nameState=convertname(observedState)
#        Xobsname.append(nameState)
#        if (observedState & Xm) <> EMPTYSET:
#            nameState=convertname(observedState)
#            Xmobs.append(nameState)
#    
#    nameobs = 'Observer of '+self.name      
#    
#    if existunobsCycle:
#        sigmaD = self.Eobs |set([delta])
#    else:
#        sigmaD = self.Eobs
#
#    auto = fsa(Xobsname,sigmaD, transition,X0obsname,Xmobs, Eobs =self.Eobs,
#                Econ = self.Econ,name=nameobs,type='observer') 
#    auto.style='observer'
#    auto.direction='UD'
#    return auto 


